#include "tab_delimited_file_to_map.h"
#include <boost/regex.hpp>
#include <boost/foreach.hpp>
#include <boost/algorithm/string.hpp>
#include <single_linkage.h>
using std::cerr;
using std::cout;
using std::string;
using std::vector;
using std::pair;
using std::deque;
using std::ostream;
using boost::algorithm::replace_all;
using boost::algorithm::split;
using boost::tuple;
using boost::make_tuple;


//ostream& operator << (ostream& os, const t_field_extractor& field_extrator)
//{
//	os << "Fields:\n";
//	assert(field_extrator.field_indices.size() == field_extrator.concatenators.size());
//	for (unsigned i = 0; i < field_extrator.field_indices.size(); ++i)
//	{
//		os	<< i << "=> \"" 
//			<<field_extrator.concatenators[i] << "\" / field #"
//			<< field_extrator.field_indices[i] << "\n";
//	}
//	os << "Terminal = \"" << field_extrator.terminal_str << "\"\n";
//	return os;
//}
 
t_field_extractor::t_field_extractor(string format_string)
{
	//debug_func_cerr(5);
	typedef string::const_iterator sciter;


	// check if \1 special marker is used within the format string
	vector<unsigned> positions_of_marker;
	sciter pos = format_string.begin();
	sciter end = format_string.end();
	while (pos != end)
	{
		if (*pos == '\1')
			positions_of_marker.push_back(pos - format_string.begin());
		++pos;
	}

	// substitute all cases of :: for '| '
	replace_all(format_string, "::", "\1 ");


	// find all field specifications
	pos = format_string.begin();
	static boost::regex field_regex("(:\\d+)");
	boost::match_results<std::string::const_iterator> what;
	boost::match_flag_type flags = boost::match_default;
	typedef pair<sciter, sciter> t_pos_pair;
	deque<t_pos_pair> field_positions;
	while(boost::regex_search(pos, end, what, field_regex, flags))   
	{
		field_positions.push_back(make_pair(what[1].first, what[1].second));
		// update flags:
		flags |= boost::match_prev_avail;
		flags |= boost::match_not_bob;
		pos = what[0].second;
	}


	// Restore original ::
	replace_all(format_string, "\1 ", "::");

	// restore original \1
	BOOST_FOREACH(unsigned position, positions_of_marker) 
	{
		format_string[position] = '\1';
		format_string[position+1] = ' ';
	}


	// break apart
	pos = format_string.begin();
	BOOST_FOREACH(const t_pos_pair& pair, field_positions) 
	{
		if (pos != pair.first)
		{
			string concatenator(pos, pair.first);
			replace_all(concatenator, "::",  ":");
			concatenators.push_back(concatenator);
		}
		else
			concatenators.push_back(string());
		// + 1 to go past :
		field_indices.push_back(atol(string(pair.first + 1, pair.second).c_str()));
		pos = pair.second;
	}

	// terminal string
	if (pos != format_string.end())
	{
		terminal_str.assign(pos, sciter(format_string.end()));
		replace_all(terminal_str, "::",  ":");
	}
	else
		terminal_str = string();
}



//________________________________________________________________________________________

//		t_identifier_map

//________________________________________________________________________________________
t_identifier_map::t_identifier_map(	t_index_string&				ids_from_,
									t_index_string&				ids_to_,
									const std::string&			comment_letters_,
									const std::string&			from_format_string,
									const std::string&			to_format_string
									):
						ids_from(ids_from_),
						ids_to(ids_to_),
						comment_letters(comment_letters_),
						cnt_ignored_entries(0),
						cnt_entries(0),
						from_format_field(from_format_string),
						to_format_field(to_format_string),
						from_error("from"),
						to_error("to")
{
}

void t_identifier_map::t_fields_error::operator()(unsigned line_cnt, unsigned fields_cnt, unsigned field_err)
{
	std::string msg("\nWhen Parsing the \"" + field_name + 
					"\" part of the identifier map.\n" +
					ultoa(fields_cnt + 1) + " columns were expected but only " + 
					ultoa(field_err) +  " column(s) found.\n");
	throw std::runtime_error(msg);
}


void t_identifier_map::operator()(std::istream& is, unsigned cnt_line, const std::string& line)
{
	// ignore if comment
	if (line.empty() || comment_letters.find(line[0]) != std::string::npos)
		return;

	// split tab-delimited fields
	vector<string> fields;
	boost::split(fields, line, std::bind2nd(std::equal_to<char>(), '\t'));

	string from_str = construct_string_from_fields(	fields,
													from_format_field,
													cnt_line,
													from_error);

	string to_str = construct_string_from_fields(	fields,
													to_format_field,
													cnt_line,
													to_error);
	cnt_lines = cnt_line;
	++cnt_entries;
		
	//
	//	Add to map only if first entry / not --unique is not specified
	//
	bool added;
	unsigned id1 = ids_from.index(from_str, added);
	unsigned id2 = ids_to.index(to_str);
	id_pairs.push_back(std::make_pair(id1, id2));
}

unsigned t_identifier_map::create_lookups(deque<t_1_to_m>&	lookup_1_to_m,
										  deque<unsigned>&	many_elements)
{
	std::deque<unsigned> temp_1_to_1(ids_from.size(), 0);
	std::deque<unsigned> counts(ids_from.size(), 0);
	std::map<unsigned, vector<unsigned> > temp_1_to_m;

	unsigned count_duplicates = 0;

	// divide all into one to many and 1_to_1 to store more efficiently
	for (unsigned i = 0; i < id_pairs.size(); ++i)
	{
		unsigned ii = id_pairs[i].first;
		unsigned jj = id_pairs[i].second;
		assert(ii < ids_from.size());
		assert(jj < ids_to.size());
		if (counts[ii]) 
		{
			// a new pair
			if(temp_1_to_1[ii] != jj)
			{
				++counts[ii];
				temp_1_to_m[ii].push_back(temp_1_to_1[ii]);
				temp_1_to_m[ii].push_back(jj);
			}

			// duplicate
			else
				++count_duplicates;
		}
		// this is the first pair
		else
		{
			temp_1_to_1[ii] = jj;
			++counts[ii];
		}
	}

	// copy 1 to 1 and 1 to m
	for (unsigned i = 0; i < counts.size(); ++i)
	{
		unsigned count = counts[i];
		// everything should have at least a count of 1
		assert(count);

		// copy 1 to 1
		if (count == 1)
		{
			// copy index to elements
			lookup_1_to_m.push_back(t_1_to_m(many_elements.size(), many_elements.size() + 1));
			// copy element
			many_elements.push_back(temp_1_to_1[i]);
		}

		// copy 1 to m
		else
		{
			assert(temp_1_to_m[i].size() > 1);

			// remove any repeats of first element
			vector<unsigned>::iterator beg = temp_1_to_m[i].begin();
			vector<unsigned>::iterator end = temp_1_to_m[i].end();
			vector<unsigned>::iterator unique_end =
				std::remove(beg + 1,  end, temp_1_to_m[i][0]);

			// sort any other elements and remove duplicates
			if (unique_end != end)
			{
				sort(beg + 1, unique_end);
				unique_end = unique(beg + 1, unique_end);
			}

			count_duplicates += end - unique_end;

			// copy index to elements
			lookup_1_to_m.push_back(t_1_to_m(many_elements.size(), many_elements.size() +
																	unique_end - beg));

			// copy elements
			copy(beg, unique_end, back_inserter(many_elements));
		}
	}

	#ifndef NDEBUG
	BOOST_FOREACH(unsigned i, many_elements) 
		assert(i < ids_to.size());
	#endif
	return count_duplicates;
	
}



void t_identifier_map::single_linkage(	deque<tuple<unsigned, unsigned> >&	clusters_1_to_1,
										deque<tuple<unsigned, t_vecu> >&	clusters_1_to_m,
										deque<tuple<t_vecu, unsigned> >&	clusters_m_to_1,
										deque<tuple<t_vecu, t_vecu> >&		clusters_m_to_m)
{
	// add offset to ids_to so that the ids do not overlap
	unsigned offset = ids_from.size() + 1;
	typedef std::pair<unsigned, unsigned> t_pair_uu;
	BOOST_FOREACH(t_pair_uu pair, id_pairs) 
		pair.second += offset; 
	std::deque<std::vector<unsigned> >	output_clusters;

	// cluster
	single_linkage_clusters(id_pairs, output_clusters);

	// sort cluster into to and from identifiers
	vector<unsigned> from;
	vector<unsigned> to;
	BOOST_FOREACH(std::vector<unsigned>& cluster, output_clusters) 
	{
		BOOST_FOREACH(unsigned i, cluster) 
		{
			if (i >= offset)
				to.push_back(i - offset);
			else
				from.push_back(i);
		}
		assert(from.size());
		assert(to.size());
		if (from.size() == 1)
		{
			// 1 to 1
			if (to.size() == 1)
			{
				clusters_1_to_1.push_back(make_tuple(from[0], to[0]));
				from.clear();
				to.clear();
			}

			// 1 to m
			else
			{
				clusters_1_to_m.push_back(make_tuple(from[0], t_vecu()));
				from.clear();
				clusters_1_to_m.back().get<1>().swap(to);
			}
		}
		else
		{
			// m to 1
			if (to.size() == 1)
			{
				clusters_m_to_1.push_back(make_tuple(t_vecu(), to[0]));
				to.clear();
				clusters_m_to_1.back().get<0>().swap(from);
			}

			// m to m
			else
			{
				clusters_m_to_m.push_back(make_tuple(t_vecu(), t_vecu()));
				clusters_m_to_m.back().get<0>().swap(from);
				clusters_m_to_m.back().get<1>().swap(to);
			}
		}
	}
}



